﻿#include "netstreamvideo.h"


#include"qtlog.h"

NetStreamVideo::NetStreamVideo(QObject *parent) : QObject(parent)
{
    m_thread = new QThread();

    this->moveToThread(m_thread);
    m_thread->start();

    connect(this,&NetStreamVideo::sig_startThread,this,&NetStreamVideo::s_startThread);
    m_audio = new AudioDecode(this);
    connect(this,&NetStreamVideo::sig_audio,m_audio,&AudioDecode::s_audio);
}

NetStreamVideo::~NetStreamVideo()
{

}

void NetStreamVideo::openUrlVideo(QString url)
{
    m_urlVideo = url;
    qDLog() << url;
    emit sig_startThread();
}

QString NetStreamVideo::getFFmpegError(int ret)
{
    char buf[1024] = { 0 };
    av_strerror(ret, buf, sizeof(buf)); //获得错误详情
    return QString(buf);
}

//void NetStreamVideo::decodeAudio(const AVPacket *pkt)
//{
//    char *c = nullptr;
//    int retSize = ToPCM(c);
//}

int NetStreamVideo::ToPCM(char *out,AVFormatContext *m_afc,AVFrame *avframe)
{
    AVCodecContext *ctx = m_afc->streams[m_audioStream]->codec;
    SwrContext *swr = nullptr;
    static void* m_aCtx = nullptr;
    if (m_aCtx == nullptr)
    {
//        SwrContext *swr = swr_alloc();
        swr = swr_alloc_set_opts(nullptr, ctx->channel_layout,
            AV_SAMPLE_FMT_S16,
            ctx->sample_rate,
            ctx->channels,
            ctx->sample_fmt,
            ctx->sample_rate,
            0, 0);
        if(swr != nullptr){
            qDLog()<<QString::fromLocal8Bit("swr_alloc_set_opts 失败:");
        }

        int ret = swr_init(swr);
        if(ret != 0){
            qDLog()<<QString::fromLocal8Bit("swr_init 失败:")<<getFFmpegError(ret);
            return 0;
        }
        m_aCtx = (void*)swr;
    }else{
        swr = (SwrContext *)m_aCtx;
    }

    if(!swr){
        qDLog()<<"2222222";
        return  0;
    }

    uint8_t *data[1];
    char ouu[1024*10] = {0};
    data[0] = (uint8_t *)ouu;

    AVFrame*m_pcm = avframe;

    int len = swr_convert(swr, data, 10000, (const uint8_t **)m_pcm->data, m_pcm->nb_samples);
    if (len <= 0)
    {
        return 0;
    }
    int outsize = av_samples_get_buffer_size(nullptr, ctx->channels, m_pcm->nb_samples, AV_SAMPLE_FMT_S16, 0);
    QByteArray a;
    a.append(ouu,outsize);

//    qDLog()<<2;
    emit sig_audio(a);
    return outsize;
}


void NetStreamVideo::s_startThread()
{

    qDLog() << m_urlVideo;
    //启动播放
    AVFormatContext *pFormatCtx = nullptr;
        AVCodecContext *pCodecCtx= nullptr,*pAudioCtx = nullptr;
        AVCodec *pCodec= nullptr;
        AVFrame *pFrame= nullptr, *pFrameRGB= nullptr;
        AVFrame *pAudioFrame= nullptr;
        AVPacket *packet= nullptr;
        uint8_t *out_buffer= nullptr;

        static struct SwsContext *img_convert_ctx= nullptr;
        int videoStream = -1,audioStream = -1;
        int i;
        int numBytes;
        int got_picture;

//        av_register_all();
        avformat_network_init();

        pFormatCtx = avformat_alloc_context();

        AVDictionary* avdic = nullptr;
        av_dict_set(&avdic, "buffer_size", "108000", 0); //设置缓存大小，1080p可将值调大
        av_dict_set(&avdic, "rtmp_transport", "udp", 0); //以udp方式打开，如果以tcp方式打开将udp替换为tcp
        av_dict_set(&avdic, "stimeout", "2000000", 0);   //设置超时断开连接时间，单位微秒
        av_dict_set(&avdic, "max_delay", "500000", 0);   //设置最大时延

        int ret = avformat_open_input(&pFormatCtx, m_urlVideo.toStdString().c_str(), nullptr, &avdic);
        if (ret != 0)
        {
            qDLog() << "avformat_open_input: faile"<<getFFmpegError(ret);
            return;
        }

        ret = avformat_find_stream_info(pFormatCtx, nullptr);
        if (ret < 0)
        {
            qDLog() << "avformat_find_stream_info: faile" <<getFFmpegError(ret);
            return;
        }


        for (i = 0; i < pFormatCtx->nb_streams; i++)
        {
            pCodecCtx = pFormatCtx->streams[i]->codec;
            if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){
                videoStream = i;
            }else if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO){
                m_audioStream = i;
                audioStream = i;
            }
        }

        if (videoStream == -1)
        {
            qDLog() << "videoStream: faile";
            return;
        }

        //查找解码器
        pCodecCtx = pFormatCtx->streams[videoStream]->codec;
        pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
        pCodecCtx->bit_rate = 0;
        pCodecCtx->time_base.num = 1;
        pCodecCtx->time_base.den = 10;
        pCodecCtx->frame_number = 1;
        if (pCodec == nullptr){
            return;
        }
        //打开解码器
        if (avcodec_open2(pCodecCtx, pCodec, nullptr) < 0)
        {
            return;
        }

        ////////////////////////////////////////////////////////////////////////
        if (m_audioStream == -1)
        {
            qDLog() << "videoStream: faile";
            return;
        }
        pAudioCtx = pFormatCtx->streams[m_audioStream]->codec;
        AVCodec *codec = avcodec_find_decoder(pAudioCtx->codec_id);   //查找解码器
        if (!codec){
            qDLog() << QString::fromLocal8Bit("没有该音频类型解码器");
            return;
        }

        ret = avcodec_open2(pAudioCtx, codec, nullptr);           //打开解码器
        if (ret != 0){
            qDLog() << QString::fromLocal8Bit("打开音频解码器失败:")<<getFFmpegError(ret);
            return;
        }
        qDLog() << QString::fromLocal8Bit("音频解码器打开成功");
        ////////////////////////////////////////////////////////////////////////

        pFrame = av_frame_alloc();
        pFrameRGB = av_frame_alloc();

        int outW = pCodecCtx->width;
        int outH = pCodecCtx->height;

        //将解码后的YUV数据转换成RGB32
        img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
            pCodecCtx->pix_fmt, outW, outH,
            AV_PIX_FMT_RGB32, SWS_BICUBIC, nullptr, nullptr, nullptr);

        numBytes = avpicture_get_size(AV_PIX_FMT_RGB32, outW, outH);

        out_buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));
        avpicture_fill((AVPicture *)pFrameRGB, out_buffer, AV_PIX_FMT_RGB32,
            outW, outH);

        int y_size = outW * outH;

        packet = (AVPacket *)malloc(sizeof(AVPacket)); //分配一个packet
        av_new_packet(packet, y_size); //分配packet的数据

        while (1)
        {
            if(!m_play){
                QThread::msleep(400);
                continue;
            }
            if (av_read_frame(pFormatCtx, packet) < 0)
            {
                qDLog() << "av_read_frame packet: 0";
                break; //这里认为视频读取完了
            }

            ///////////////////////////////////
            if (pAudioFrame == nullptr)
                pAudioFrame = av_frame_alloc();
            AVFrame *frame = pAudioFrame;  //指针传值
//            int re = avcodec_send_packet(pFormatCtx->streams[packet->stream_index]->codec, packet);
//            if (re != 0){
//                qDLog() << "avcodec_send_packet packet: 0" <<getFFmpegError(re);
//                return;
//            }
//            re = avcodec_receive_frame(pFormatCtx->streams[packet->stream_index]->codec, frame);
//            if (re != 0){
//                qDLog() << "avcodec_receive_frame packet: 0"<<getFFmpegError(re);
//                return;
//            }

//            if (packet->stream_index == videoStream)
//                YuvToRGB(pFormatCtx,frame,0,0);
//            if (packet->stream_index == audioStream){
//                char *p;
//                ToPCM(p,pFormatCtx,frame);
//            }

            ///////////////////////////////////

            if (packet->stream_index == videoStream)
            {
                int ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);

                if (ret < 0)
                {
                    qDLog() << "avcodec_decode_video2 packet: 0";
                    return;
                }

                if (got_picture)
                {
                    sws_scale(img_convert_ctx,
                        (uint8_t const * const *)pFrame->data,
                        pFrame->linesize, 0, outH, pFrameRGB->data,
                        pFrameRGB->linesize);

                    //把这个RGB数据 用QImage加载
                    QImage tmpImg((uchar *)out_buffer, outW, outH, QImage::Format_RGB32);
//                    QImage image = tmpImg.copy();  //把图像复制一份 传递给界面显示

                    emit sig_imageFrame(tmpImg);  //发送图像
                }
            }//else if(packet->stream_index == audioStream){
//                char *p=nullptr;

//                int ret = avcodec_decode_video2(pAudioCtx, pFrame, &got_picture, packet);
//                qDLog() << packet->stream_index << ret <<getFFmpegError(ret);
//                ToPCM(p,pFormatCtx,pFrame);
//            }
            av_free_packet(packet); //释放资源,否则内存会一直上升
//            QThread::msleep(10); //停一停  不然放的太快了
        }

//        av_free(out_buffer);
//        av_free(pFrameRGB);
//        avcodec_close(pCodecCtx);
//        avformat_close_input(&pFormatCtx);
}

bool NetStreamVideo::YuvToRGB(AVFormatContext *m_afc,AVFrame *avframe, int outweight, int outheightt)
{
    static SwsContext * m_cCtx = nullptr;
    AVCodecContext *videoCtx = m_afc->streams[m_audioStream]->codec;
    m_cCtx = sws_getCachedContext(m_cCtx, videoCtx->width, videoCtx->height,
        videoCtx->pix_fmt,  //像素点的格式
        videoCtx->width, videoCtx->height,  //目标宽度与高度
        AV_PIX_FMT_BGRA,  //输出的格式
        SWS_BICUBIC,  //算法标记
        nullptr, nullptr, nullptr
        );

    if (!m_cCtx){
        return false;
    }

    static QImage *imge = nullptr;
    static uchar *buf = nullptr;
    if(!imge){
        buf = new uchar[videoCtx->width * videoCtx->height * 4];
        imge = new QImage(buf, videoCtx->width, videoCtx->height, QImage::Format_ARGB32);
    }

    char *pbuf = (char*)imge->bits();
    uint8_t *data[AV_NUM_DATA_POINTERS] = { 0 };
    data[0] = (uint8_t *)pbuf;
    int linesize[AV_NUM_DATA_POINTERS] = { 0 };
    linesize[0] = videoCtx->width * 4;

    //返回转码后的高度
    int h = sws_scale(m_cCtx, avframe->data, avframe->linesize, 0, videoCtx->height,
        data,
        linesize
        );
    if(h >0)
        emit sig_imageFrame(*imge);
    return true;
}
